package app.domain.server;

import java.util.HashMap;
import java.util.Map;

import app.domain.order.Order;
import app.domain.order.OrderQueue;
import app.domain.order.util.OrderType;
import app.domain.order.util.Side;
import app.domain.order.util.Ticket;
import app.domain.orderBook.BuyOrderBook;
import app.domain.orderBook.OrderBook;
import app.domain.orderBook.OrderBookMap;
import app.domain.orderBook.SellOrderBook;
import app.domain.server.util.ClientData;
import app.domain.server.util.MarketStockPrices;
import app.domain.server.util.MessageSorter;
import app.domain.util.Logger;
import app.domain.util.StockProtocol;

public class Server {
	private Map<String,ClientData> clients = new HashMap<>();
	private MarketStockPrices marketStockPrices = new MarketStockPrices();
	private OrderBookMap buyOrderBookMap = new OrderBookMap();
	private OrderBookMap sellOrderBookMap = new OrderBookMap();
	private OrderQueue orderQueue = new OrderQueue();
	private MessageSorter messageSorter;
	
	public Server() {
		messageSorter = new MessageSorter(this);
		for(Ticket t : Ticket.values()) {
			buyOrderBookMap.put(t, new BuyOrderBook());
			sellOrderBookMap.put(t,new SellOrderBook());
			marketStockPrices.put(t,50.);
		}
		new Thread(new Runnable() {
			@Override
			public void run() {
				while(true) {						
					Order order = orderQueue.remove();
					if(order.getOrderType() == OrderType.NEW) {
						messageSorter.addNewOrderMessage(order);
						match(order);
						System.out.println("------------------------------------------");
						if(order.getAmount() != 0) {
							putOrder(order);
						}						
					} else if(order.getOrderType() == OrderType.CANCEL) {
						cancelOrder(order);
					} else if(order.getOrderType() == OrderType.DELETE_CLIENT) {
						deleteClientOrders(order.getClientId());
					}						
				}
			}
		}).start();
	}
	
	private void putOrder(Order order) {
		if(order.getSide() == Side.B) {
			buyOrderBookMap.putOrder(order);
		} else if(order.getSide() == Side.S) {
			sellOrderBookMap.putOrder(order);
		}
	}
	
	private void cancelOrder(Order order) {
		if(buyOrderBookMap.removeOrder(order)) {
			messageSorter.addCancelMessage(order);
		} else if(sellOrderBookMap.removeOrder(order)) {
			messageSorter.addCancelMessage(order);
		} else {
			messageSorter.addNoCancelMessage(order);			
		}
	}
	
	private void deleteClientOrders(String clientId) {
		buyOrderBookMap.deleteClientOrders(clientId);
		sellOrderBookMap.deleteClientOrders(clientId);
		Logger.logServerInfo("all " + clientId + " orders were canceled");
	}
	
	private void match(Order order) {
		if(order.getSide() == Side.B) {
			OrderBook orderBook = sellOrderBookMap.remove(order.getTicket());
			sellOrderBookMap.put(order.getTicket(), matchProcess(order, orderBook));
		} else if(order.getSide() == Side.S) {
			OrderBook orderBook = buyOrderBookMap.remove(order.getTicket());
			buyOrderBookMap.put(order.getTicket(), matchProcess(order, orderBook));
		}
	}

	private OrderBook matchProcess(Order order, OrderBook orderBook) {
		while ((orderBook.size() > 0) && (order.getAmount() > 0)) {
			if(order.getPrice()*order.getSide().sgn() <= orderBook.get(0).getPrice()*order.getSide().sgn()) {
				marketStockPrices.update(order, orderBook.get(0));
				if(order.getAmount() < orderBook.get(0).getAmount()) {
					orderBook.get(0).changeAmount(order.getAmount());
					int amount = order.getAmount();
					order.setAmount(0);
					messageSorter.addMatchMessage(order, orderBook.get(0),amount,0,StockProtocol.FULL_MATCH);
					messageSorter.addMatchMessage(orderBook.get(0),order,amount,orderBook.get(0).getAmount(),StockProtocol.PART_MATCH);
				} else if(order.getAmount() > orderBook.get(0).getAmount()) {
					Order tempOrder = orderBook.get(0);
					order.changeAmount(tempOrder.getAmount());
					if (tempOrder.getAmount() != 0) {
						messageSorter.addMatchMessage(order,tempOrder,tempOrder.getAmount(),order.getAmount(),StockProtocol.PART_MATCH);
						messageSorter.addMatchMessage(tempOrder,order,tempOrder.getAmount(),0,StockProtocol.FULL_MATCH);												
					}
					orderBook.get(0).changeAmount(tempOrder.getAmount());
					orderBook.remove(0);
				} else {
					Order tempOrder = orderBook.get(0);
					order.setAmount(0);
					if(tempOrder.getAmount() != 0) {
						messageSorter.addMatchMessage(order,tempOrder,tempOrder.getAmount(),0,StockProtocol.FULL_MATCH);
						messageSorter.addMatchMessage(tempOrder,order,tempOrder.getAmount(),0,StockProtocol.FULL_MATCH);												
					}
					orderBook.get(0).changeAmount(tempOrder.getAmount());
					orderBook.remove(0);
				}
			} else break;
		}
		return orderBook;
	}
	
	public OrderQueue getOrderQueue() {
		return orderQueue;
	}
	
	public Map<String, ClientData> getClients() {
		return clients;
	}
	
	public MarketStockPrices getMarketStockPrices() {
		return marketStockPrices;
	}
	
}
